<?php

namespace think\filesystem\Adapter;

use League\Flysystem\Config;
use League\Flysystem\FileAttributes;
use League\Flysystem\FilesystemAdapter;
use Qcloud\Cos\Client;
use QCloud\COSSTS\Sts;

/**
 * Class CosAdapter.
 */
class CosAdapter implements FilesystemAdapter
{
    /**
     * @var Client
     */
    protected $cosClient;

    /**
     * @var Sts
     */
    protected $sts;

    /**
     * 配置参数
     * @var array
     */
    protected $config = [
        'type' => 'cos',
        'region' => 'ap-beijing',
        'credentials' => [
            'appId' => '',
            'secretId' => '',
            'secretKey' => '',
        ],
        'bucket' => '',
        "domain" => "",
        'cdn' => '',
        'scheme' => 'https',
    ];

    /**
     * CosAdapter constructor.
     * @param $config
     */
    public function __construct($config)
    {
        $this->config = array_merge($this->config, $config);
        $this->config['timeout'] = $this->config['timeout'] ?? 3600;
        $this->config['connect_timeout'] = $this->config['connect_timeout'] ?? 60;
        $this->config['read_from_cdn'] = $this->config['read_from_cdn'] ?? false;
    }

    /**
     * 上传文件
     * @param $path
     * @param $contents
     * @param Config $config
     * @return void
     */
    public function write($path, $contents, Config $config): void
    {
        $options = [];
        if (!empty($config->get('params'))) {
            $options = $config->get('params');
        }
        $options['ACL'] = $config->get('ACL') ?? 'public-read';
        $this->getObjClient()->upload($this->config['bucket'], $path, $contents, $options);
    }

    /**
     * 写入文件流
     * @param $path
     * @param $contents
     * @param Config $config
     * @return void
     */
    public function writeStream($path, $contents, Config $config): void
    {
        $this->write($path, \stream_get_contents($contents), $config);
    }

    /**
     * 移动文件
     * @param string $source
     * @param string $destination
     * @param Config $config
     * @return void
     */
    public function move(string $source, string $destination, Config $config): void
    {
        //拷贝object到指定地方
        $this->copy($source, $destination, $config);
        //删除指定object
        $this->delete($source);
    }

    /**
     * 拷贝文件
     * @param string $source
     * @param string $destination
     * @param Config $config
     * @return void
     */
    public function copy(string $source, string $destination, Config $config): void
    {
        $copyRegion = empty($config->get('copy_region')) ? $this->config['region'] : $config->get('copy_region');
        $copyBucket = empty($config->get('copy_bucket')) ? $this->config['bucket'] : $config->get('copy_bucket');
        $this->getObjClient()->Copy(
            $this->config['bucket'],
            $source,
            [
                'Region' => $copyRegion,
                'Bucket' => $copyBucket,
                'Key' => $destination,
            ]
        );
    }

    /**
     * 删除文件
     * @param string $path
     * @return void
     */
    public function delete(string $path): void
    {
        $this->getObjClient()()->deleteObject($this->config['bucket'], $path);
    }

    /**
     * 删除目录
     * @param string $path
     * @return void
     */
    public function deleteDirectory(string $path): void
    {
    }

    /**
     * 创建目录
     * @param string $path
     * @param Config $config
     * @return void
     */
    public function createDirectory(string $path, Config $config): void
    {
    }

    /**
     * 文件是否存在
     * @param string $path
     * @return bool
     */
    public function fileExists(string $path): bool
    {
        return $this->getObjClient()->doesObjectExist($this->config['bucket'], $path);
    }

    /**
     * 读取文件
     * @param string $path
     * @return string
     */
    public function read(string $path): string
    {
        return $this->getObjClient()->headObject([
            "Bucket" => $this->config['bucket'],
            "Key" => $path,
        ]);
    }

    /**
     * 读取文件流
     * @param string $path
     * @return resource
     */
    public function readStream(string $path)
    {
        return fopen($this->getUrl($path), 'r');
    }

    /**
     * 获取指定目录下文件列表
     * @param string $path
     * @param bool $deep
     * @return iterable
     */
    public function listContents(string $path, bool $deep): iterable
    {
        $response = $this->getObjClient()->listObjects([
            'Bucket' => $this->config['bucket'],
            'Prefix' => $path,
            'Delimiter' => $deep ? '' : '/',
        ]);
        $objectList = $response['Contents'] ?? [];
        foreach ($objectList as $file) {
            $file['LastModified'] = empty($file['LastModified']) ? null : strtotime($file['LastModified']);
            yield $this->normalizeFileInfo($file);
        }
    }

    /**
     * 获取metadata信息
     * @param string $path
     * @return FileAttributes
     */
    public function mimeType(string $path): FileAttributes
    {
        return $this->getMetadata($path);
    }

    /**
     * 文件meta信息
     * @param string $path
     * @return FileAttributes
     */
    public function fileSize(string $path): FileAttributes
    {
        return $this->getMetadata($path);
    }

    /**
     * 上次修改信息
     * @param string $path
     * @return FileAttributes
     */
    public function lastModified(string $path): FileAttributes
    {
        return $this->getMetadata($path);
    }

    /**
     * 查看文件权限
     * @param string $path
     * @return FileAttributes
     */
    public function visibility(string $path): FileAttributes
    {
        throw new \Exception($path);
    }

    /**
     * 设置读写权限
     * @param string $path
     * @param string $visibility public|private
     * @return void
     */
    public function setVisibility(string $path, string $visibility): void
    {

    }

    /**
     * @param $path
     * @return string
     */
    public function getUrl($path)
    {
        return $this->config['scheme'] . '://' . $this->config['cdn'] . "/" . $path;
    }

    /**
     * 获取配置参数
     * @return array
     */
    public function getConfig()
    {
        return $this->config;
    }

    /**
     * 从指定URL抓取资源，并将该资源存储到指定空间中
     * @param string $url
     * @param string $path 指定为"source"时按目标路径存储
     * @return string
     */
    public function fetch(string $url, string $path = 'source')
    {
        ini_set("user_agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/102.0.0.0 Safari/537.36");
        $contents = file_get_contents($url); //读取文件内容
        $parsedUrl = parse_url($url);
        $filePath = ltrim(pathinfo($parsedUrl['path'], PATHINFO_DIRNAME), '/'); //文件路径
        $fileName = pathinfo($parsedUrl['path'], PATHINFO_BASENAME);            //文件名
        //$fileExtension = pathinfo($parsedUrl['path'], PATHINFO_EXTENSION); //文件后缀
        $path = $path == "source" ? $filePath . '/' . $fileName : $path;
        $this->getObjClient()->upload($this->config['bucket'], $path, $contents);
        return $path;
    }

    /**
     * 获取cosClient
     * @return Client
     */
    public function getObjClient()
    {
        return $this->cosClient ?: $this->cosClient = new Client($this->config);
    }

    /**
     * 返回上传签名
     * @param $key
     * @return string
     */
    public function getUploadToken($key)
    {
        return $this->getObjClient()->getObjectUrl($this->config['bucket'], $key);
    }

    /**
     * 获取sts
     * @return Sts
     */
    public function getSts()
    {
        return $this->sts ?: $this->sts = new Sts();
    }

    /**
     * @param $path
     * @return FileAttributes
     */
    public function getMetadata($path)
    {
        $response = $this->getObjClient()->headObject([
            'Bucket' => $this->config['bucket'],
            'Key' => $path,
        ]);
        $meta = $response->toArray();
        $stats = [
            'Key' => $path,
            'Size' => $meta['ContentLength'] ?? null,
            'LastModified' => empty($meta['LastModified']) ? null : strtotime($meta['LastModified']),
            'Content-Type' => empty($meta['ContentType']) ? null : $meta['ContentType'],
        ];
        return $this->normalizeFileInfo($stats);
    }

    /**
     * 获取文件信息 转数组：fileAttributes->jsonSerialize()
     * @param array $stats
     * @return FileAttributes
     */
    protected function normalizeFileInfo(array $stats)
    {
        return new FileAttributes(
            $stats['Key'],
            $stats['Size'] ?? null,
            null,
            $stats['LastModified'] ?? null,
            $stats['Content-Type'] ?? null
        );
    }

    /**
     * 获取临时密钥
     * @param $options
     * @return null
     */
    public function getTempKeys($options)
    {
        $sts = new Sts();
        $config = [
            'url' => 'https://sts.tencentcloudapi.com/',
            'domain' => 'sts.tencentcloudapi.com',
            'proxy' => $options['proxy'] ?? '',
            'secretId' => $this->config['credentials']['secretId'],
            'secretKey' => $this->config['credentials']['secretKey'],
            'bucket' => $this->config['bucket'],
            'region' => $this->config['region'],
            'durationSeconds' => $options['proxy'] ?? 3600, // 密钥有效期
            'allowPrefix' => $options['allowPrefix'] ?? ['*'],
            // 密钥的权限列表。简单上传和分片需要以下的权限，其他权限列表请看 https://cloud.tencent.com/document/product/436/31923
            'allowActions' => $options['allowActions'] ?? ['name/cos:PutObject', 'name/cos:PostObject'],
            'condition' => $options['condition'] ?? [],
        ];
        $data = $sts->getTempKeys($config);
        $data['bucket'] = $this->config['bucket'];
        $data['region'] = $this->config['region'];
        return $data;
    }

}